package com.golf.server.game;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.golf.core.course.Course;
import com.golf.core.course.bodies.Ball;
import com.golf.core.course.bodies.CourseBody;
import com.golf.core.course.bodies.Vector;
import com.golf.core.course.exceptions.CourseLoadException;
import com.golf.core.course.loader.CoursePicker;
import com.golf.core.game.score.Score;
import com.golf.core.game.score.ScoreBoard;
import com.golf.core.player.Player;
import com.golf.server.engine.phys.Engine;

/**
 * A class that represents a game, and how to play it.
 * 
 * @author gareth
 */
public class Game implements Runnable
{
    
    /** The rules that dictate how the game is played. */
    private final GameRules rules;

    /** The scoreboard for this game. */
    private final ScoreBoard scoreBoard;

    /** The list of players participating in the game. */
    private final List<Player> players;

    /** The map of players to balls. */
    private Map<Player, Ball> balls;
    
    
    /** The physics engine associated with this game. */
    private Engine engine;
    
    
    /**
     * Create a game for a list of players, and some game rules.
     * 
     * @param playerList The list of players.
     * @param rules The rules for the game.
     */
    public Game(final List<Player> playerList, GameRules rules)
    {
        // Set the game rules.
        this.rules = rules;
        
        // Create the player list from the given players.
        players = new ArrayList<Player>(playerList.size());
        players.addAll(playerList);

        // Initialize the scoreboard.
        scoreBoard = new ScoreBoard(players);
        balls = new HashMap<Player, Ball>();
    }
    
    
    /**
     * {@inheritDoc}
     */
    public void run()
    {
        int courseCounter = 0;
        while (courseCounter++ < rules.getCourses())
        {
            try
            {
                // Choose the next course to play.
                final Course course = CoursePicker.getInstance().randomCourse();
                scoreBoard.newCourse(course);
                
                // Setup the course (place players balls, and start engine).
                setupCourse(course);
    
                // Play the course.
                playCourse(course);
            }
            catch (CourseLoadException cle)
            {
                // If the system couldn't load the course, skip round and try the next one.
                
                // FIXME: Add error handling, and fail somehow.
            }
        }
    }
    

    /**
     * Setup a course, place the players' balls, and start the engine. 
     * 
     * @param course The course to setup with.
     */
    public void setupCourse(final Course course)
    {
        // Notify players of the new course that they are about to play.
        notifyPlayers(course);
        
        // Assign a ball to each player.
        balls.clear();
        for (Player player : players)
        {
            // Give each player a ball ...
            final Ball ball = new Ball(player.name() + "Ball", course.getStartPosition());
            balls.put(player, ball);
            //player.setBall(ball);
            
            // Place the balls on the course.
            course.placeBalls(balls.values());
        }

        // Create the physics engine for the course.
        engine = new Engine(course);
        engine.reset();
    }
    

    /** 
     * Play a course.
     * 
     * @param course The course that is to be played.
     */
    private void playCourse(final Course course)  
    {
        boolean atLeastOnePlayerLeft = true;
        while (atLeastOnePlayerLeft)
        {
            // Continue as long as at least one player is still playing.
            atLeastOnePlayerLeft = false;
            for (Player player : players)
            {
                // For each player that has NOT reached the max score, or have NOT putted ...
                if (scoreBoard.getCourseScore(player, course) < rules.getMaxStrokes())
                {
                    // Wait for the player to choose a shot.
                    final Vector shot = player.chooseShot();
                    
                    // Play the shot, and then increment the players score.
                    engine.playShot(balls.get(player), shot);
                    scoreBoard.chalkShot(player);
                
                    atLeastOnePlayerLeft = true;
                }
                
                // TEMPORARY -WILL BE REMOVED.
                if (atLeastOnePlayerLeft)
                {
                    // Sgow each players scores.
                    final Map<String, Score> scores = scoreBoard.getScores(player);
                    for (Entry<String, Score> x : scores.entrySet())
                    {
                        System.out.println("Player: " + player.name() + " on course: " + x.getKey() + " scoring " + x.getValue().getScore());
                    }
                    
                    System.out.println("--------");
                }
            }
        }
    }

    
    /**
     * Notify players that a new course has been started.
     * 
     * @param course The course that the players should load.
     */
    private void notifyPlayers(final Course course)
    {
        System.out.println("----- Starting new course ------");
        System.out.println("Notifying players of new course: " + course.name());
    }

    
    /**
     * Notify players that a body has moved on the course.
     * 
     * 
     */
    private void raiseBodyMotionEvent(final CourseBody body, Vector motion)
    {
        System.out.println("----- Body movement detected ------");
        System.out.println("Notifying players of new object locations: " + body.name());
    }    
    
}